// thread standard header
#pragma once
#ifndef _THREAD_
#define _THREAD_
#ifndef RC_INVOKED
#include <chrono>
#include <memory>
#include <process.h>
#include <thr/xthreads.h>
#include <tuple>

#ifdef _M_CEE_PURE
#error <thread> is not supported when compiling with /clr:pure.
#endif // _M_CEE_PURE

#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new

_STD_BEGIN
class thread { // class for observing and managing threads
public:
    class id;

    using native_handle_type = void*;

    thread() noexcept : _Thr{} { // construct with no thread
    }

private:
    template <class _Tuple, size_t... _Indices>
    static unsigned int __stdcall _Invoke(void* _RawVals) noexcept { // enforces termination
        // adapt invoke of user's callable object to _beginthreadex's thread procedure
        const unique_ptr<_Tuple> _FnVals(static_cast<_Tuple*>(_RawVals));
        _Tuple& _Tup = *_FnVals;
        _STD invoke(_STD move(_STD get<_Indices>(_Tup))...);
        _Cnd_do_broadcast_at_thread_exit(); // TRANSITION, ABI
        return 0;
    }

    template <class _Tuple, size_t... _Indices>
    _NODISCARD static constexpr auto _Get_invoke(
        index_sequence<_Indices...>) noexcept { // select specialization of _Invoke to use
        return &_Invoke<_Tuple, _Indices...>;
    }

public:
    template <class _Fn, class... _Args, class = enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>>>
    explicit thread(_Fn&& _Fx, _Args&&... _Ax) { // construct with _Fx(_Ax...)
        using _Tuple                 = tuple<decay_t<_Fn>, decay_t<_Args>...>;
        auto _Decay_copied           = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
        constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});
        _Thr._Hnd =
            reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
        if (_Thr._Hnd == nullptr) { // failed to start thread
            _Thr._Id = 0;
            _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);
        } else { // ownership transferred to the thread
            (void) _Decay_copied.release();
        }
    }

    ~thread() noexcept { // clean up
        if (joinable()) {
            _STD terminate();
        }
    }

    thread(thread&& _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) { // move from _Other
    }

    thread& operator=(thread&& _Other) noexcept { // move from _Other

        if (joinable()) {
            _STD terminate();
        }

        _Thr = _STD exchange(_Other._Thr, {});
        return *this;
    }

    thread(const thread&) = delete;
    thread& operator=(const thread&) = delete;

    void swap(thread& _Other) noexcept { // swap with _Other
        _STD swap(_Thr, _Other._Thr);
    }

    _NODISCARD bool joinable() const noexcept { // return true if this thread can be joined
        return _Thr._Id != 0;
    }

    void join() { // join thread
        if (!joinable()) {
            _Throw_Cpp_error(_INVALID_ARGUMENT);
        }

        if (_Thr._Id == _Thrd_id()) {
            _Throw_Cpp_error(_RESOURCE_DEADLOCK_WOULD_OCCUR);
        }

        if (_Thrd_join(_Thr, nullptr) != _Thrd_success) {
            _Throw_Cpp_error(_NO_SUCH_PROCESS);
        }

        _Thr = {};
    }

    void detach() { // detach thread
        if (!joinable()) {
            _Throw_Cpp_error(_INVALID_ARGUMENT);
        }

        _Check_C_return(_Thrd_detach(_Thr));
        _Thr = {};
    }

    _NODISCARD id get_id() const noexcept;

    _NODISCARD static unsigned int hardware_concurrency() noexcept { // return number of hardware thread contexts
        return _Thrd_hardware_concurrency();
    }

    _NODISCARD native_handle_type native_handle() { // return Win32 HANDLE as void *
        return _Thr._Hnd;
    }

private:
    _Thrd_t _Thr;
};

namespace this_thread {
    _NODISCARD thread::id get_id() noexcept;

    inline void yield() noexcept { // give up balance of time slice
        _Thrd_yield();
    }

    inline void sleep_until(const xtime* _Abs_time) { // sleep until _Abs_time
        _Thrd_sleep(_Abs_time);
    }

    template <class _Clock, class _Duration>
    inline void sleep_until(const chrono::time_point<_Clock, _Duration>& _Abs_time) { // sleep until time point
        for (;;) {
            const auto _Now = _Clock::now();
            if (_Abs_time <= _Now) {
                return;
            }

            _CSTD xtime _Tgt;
            (void) _To_xtime_10_day_clamped(_Tgt, _Abs_time - _Now);
            _Thrd_sleep(&_Tgt);
        }
    }

    template <class _Rep, class _Period>
    inline void sleep_for(const chrono::duration<_Rep, _Period>& _Rel_time) { // sleep for duration
        sleep_until(chrono::steady_clock::now() + _Rel_time);
    }
} // namespace this_thread

class thread::id { // thread id
public:
    id() noexcept : _Id(0) { // id for no thread
    }

private:
    id(_Thrd_id_t _Other_id) : _Id(_Other_id) { // construct from unique id
    }

    _Thrd_id_t _Id;

    friend thread::id thread::get_id() const noexcept;
    friend thread::id this_thread::get_id() noexcept;
    friend bool operator==(thread::id _Left, thread::id _Right) noexcept;
    friend bool operator<(thread::id _Left, thread::id _Right) noexcept;
    template <class _Ch, class _Tr>
    friend basic_ostream<_Ch, _Tr>& operator<<(basic_ostream<_Ch, _Tr>& _Str, thread::id _Id);
    friend hash<thread::id>;
};

_NODISCARD inline thread::id thread::get_id() const noexcept { // return id for current thread
    return _Thr._Id;
}

_NODISCARD inline thread::id this_thread::get_id() noexcept { // return id for current thread
    return _Thrd_id();
}

inline void swap(thread& _Left, thread& _Right) noexcept { // swap _Left with _Right
    _Left.swap(_Right);
}

_NODISCARD inline bool operator==(
    thread::id _Left, thread::id _Right) noexcept { // return true if _Left and _Right identify the same thread
    return _Left._Id == _Right._Id;
}

_NODISCARD inline bool operator!=(
    thread::id _Left, thread::id _Right) noexcept { // return true if _Left and _Right do not identify the same thread
    return !(_Left == _Right);
}

_NODISCARD inline bool operator<(thread::id _Left, thread::id _Right) noexcept { // return true if _Left precedes _Right
    return _Left._Id < _Right._Id;
}

_NODISCARD inline bool operator<=(
    thread::id _Left, thread::id _Right) noexcept { // return true if _Left precedes or equals _Right
    return !(_Right < _Left);
}

_NODISCARD inline bool operator>(thread::id _Left, thread::id _Right) noexcept { // return true if _Left follows _Right
    return _Right < _Left;
}

_NODISCARD inline bool operator>=(
    thread::id _Left, thread::id _Right) noexcept { // return true if _Left follows or equals _Right
    return !(_Left < _Right);
}

template <class _Ch, class _Tr>
inline basic_ostream<_Ch, _Tr>& operator<<(basic_ostream<_Ch, _Tr>& _Str, thread::id _Id) { // insert id into stream
    return _Str << _Id._Id;
}

// STRUCT TEMPLATE SPECIALIZATION hash
template <>
struct hash<thread::id> { // hash functor for thread::id
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef thread::id argument_type;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef size_t result_type;

    _NODISCARD size_t operator()(const thread::id _Keyval) const
        noexcept { // hash _Keyval to size_t value by pseudorandomizing transform
        return _Hash_representation(_Keyval._Id);
    }
};
_STD_END

#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)
#pragma pack(pop)
#endif // RC_INVOKED
#endif // _THREAD_

/*
 * Copyright (c) by P.J. Plauger. All rights reserved.
 * Consult your license regarding permissions and restrictions.
V6.50:0009 */
